Was ist eine gute Grafik?

Darstellung von David McCandless in Knowledge is Beautiful (2014).

Übung: What makes a bad plot?

Jede Gruppe bekommt eine Abbildung.

  1. Diskutiert in der Gruppe, was bei der Visualisierung schief läuft.
  2. Stellt den anderen Gruppen eure Ergebnisse vor.

Bild gefunden auf Reddit.

Informationen

  • Integrität der Datenquelle
  • Integrität der Darstellung

Fehlender Kontext

Election Plot

https://storymaps.arcgis.com/stories/0e636a652d44484b9457f953994b212b

## https://dataverse.harvard.edu/file.xhtml?fileId=10851934&version=9.1

library(gerda)
library(sf)

# List available datasets
available_data <- gerda_data_list()
|data_name           |description                                                                         |
|:-------------------|:-----------------------------------------------------------------------------------|
|municipal_unharm    |Local elections at the municipal level (1990-2020, unharmonized).                   |
|municipal_harm      |Local elections at the municipal level (1990-2020, harmonized).                     |
|state_unharm        |State elections at the municipal level (2006-2019, unharmonized).                   |
|state_harm          |State elections at the municipal level (2006-2019, harmonized).                     |
|federal_muni_raw    |Federal elections at the municipal level (1980-2021, raw data).                     |
|federal_muni_unharm |Federal elections at the municipal level (1980-2021, unharmonized).                 |
|federal_muni_harm   |Federal elections at the municipal level (1990-2021, harmonized).                   |
|federal_cty_unharm  |Federal elections at the county level (1953-2021, unharmonized).                    |
|federal_cty_harm    |Federal elections at the county level (1990-2021, harmonized).                      |
|ags_crosswalks      |Crosswalks for municipalities (1990-2021).                                          |
|cty_crosswalks      |Crosswalks for counties (1990-2021).                                                |
|ags_area_pop_emp    |Crosswalk covariates (area, population, employment) for municipalities (1990-2021). |
|cty_area_pop_emp    |Crosswalk covariates (area, population, employment) for counties (1990-2021).       |
geom_dat <- sf::read_sf(here::here("sessions", "good_plots", "data", "shapefiles"), layer = "VG250_GEM" )
geom_dat_lan <- sf::read_sf(here::here("sessions", "good_plots", "data", "shapefiles"), layer = "VG250_LAN" ) %>% 
  dplyr::filter(GF == 4)


elect_dat <- load_gerda_web("federal_muni_unharm", verbose = FALSE, file_format = "rds") %>% 
  dplyr::filter(election_year == 2025) %>% 
  dplyr::select(ags, county, cdu_csu, spd, gruene, afd, bsw, fdp, linke_pds, number_voters, eligible_voters) 

geom_elect_dat <- geom_dat %>%
  left_join(elect_dat, by = c("AGS" = "ags")) %>%
  dplyr::filter(GF == 4)


plot_fed_map <- function(fill_var, legend_label, fill_palette, limits, plot_title = NULL) {
  ggplot() +
    geom_sf(data = geom_elect_dat, mapping = aes(fill = .data[[fill_var]]), colour = NA) +
    geom_sf(data = geom_dat_lan, fill = NA, colour = "grey30", linewidth = 0.2) +
    coord_sf() +
    theme_minimal() +
    theme(
      panel.grid.major = element_blank(),
      panel.grid.minor = element_blank(),
      title = element_text(size = 9),
      axis.title.x = element_blank(), axis.text.x = element_blank(), axis.ticks.x = element_blank(),
      axis.title.y = element_blank(), axis.text.y = element_blank(), axis.ticks.y = element_blank(),
      legend.position = "bottom", legend.text = element_text(size = 14), legend.key.size = unit(0.6, "cm"),
      plot.margin = margin(t = 0, r = 0, b = 0, l = 0),
      legend.box.margin = margin(t = -25, r = 0, b = 0, l = 0)
    ) +
    scale_fill_distiller(NULL,
                         palette = fill_palette,
                         na.value = "white",
                         direction = 1,
                         limits = limits,
                         guide = guide_legend(
                           keyheight = unit(4, units = "mm"), keywidth = unit(18, units = "mm"),
                           label.position = "bottom", title.position = "top", nrow = 1
                         )
    ) +
    # Add title if provided
    labs(title = plot_title)
}

plot_fed_map("afd", "spd", "Blues", c(0,1))

geom_elect_max <- geom_elect_dat %>% 
  pivot_longer(cols = c("cdu_csu", "spd", "gruene", "afd", "bsw", "linke_pds", "fdp"), values_to = "result", 
               names_to = "partei") %>% 
  filter(!is.na(result)) %>% 
  group_by(AGS) %>% 
  slice_max(result) 
  
  
 ggplot() +
    geom_sf(data = geom_elect_max, mapping = aes(fill = partei), colour = NA) +
    geom_sf(data = geom_dat_lan, fill = NA, colour = "grey30", linewidth = 0.2) +
    coord_sf() +
    theme_minimal() +
    theme(
      panel.grid.major = element_blank(),
      panel.grid.minor = element_blank(),
      title = element_text(size = 9),
      axis.title.x = element_blank(), axis.text.x = element_blank(), axis.ticks.x = element_blank(),
      axis.title.y = element_blank(), axis.text.y = element_blank(), axis.ticks.y = element_blank(),
      legend.position = "bottom", legend.text = element_text(size = 14), legend.key.size = unit(0.6, "cm"),
      plot.margin = margin(t = 0, r = 0, b = 0, l = 0),
      legend.box.margin = margin(t = -25, r = 0, b = 0, l = 0)
    ) +
    scale_fill_manual(values = c("cdu_csu" = "black", 
                           "spd" = "red", 
                            "gruene" = "green", 
                           "linke_pds" = "purple", 
                            "fdp" =  "yellow", 
                            "afd" = "blue")) +
    labs(title = "Partei mit den meisten Zweitstimmen per Gemeinde")

Alternativen

Bild von stroymaps.

Kartogramm

library(cartogram)

## Ansonsten mit county oder sogar nur Bundesland?

cartogram_dat <- cartogram_cont(geom_elect_max,
                 weight = "number_voters")

saveRDS(here::here("sessions", "good_plots", "data", "cartogram_dat.rds"))
# https://r-charts.com/spatial/cartogram-ggplot2/

library(cartogram)

readRDS(here::here("sessions", "good_plots", "data", "cartogram_dat.rds"))

ggplot(cartogram_dat) +
  geom_sf()

Von Costamiri - Eigenes WerkMap based on: Shapefiles by bundeswahlleiterin.de (Geometrie der Wahlkreise im Koordinatensystem UTM32 generalisiert) © Die Bundeswahlleiterin, Statistisches Bundesamt, Wiesbaden 2024, Wahlkreiskarte für die Wahl zum 21. Deutschen Bundestag; Grundlage der Geoinformationen © Geobasis-DE / BKG 2024Data based on: Bundestagswahl 2025: Open-Data-Angebot der Bundeswahlleiterin by bundeswahlleiterin.de, CC BY-SA 4.0, Link

:::{.column width=“50%}

:::

Grafiken von zeit.de.

Alternativen

Dafür die Plots selber nachbauen?

Achsen

Konventionen

776.013 Home protection; use or threatened use of deadly force; presumption of fear of death or great bodily harm.— (1) A person who is in a dwelling or residence in which the person has a right to be has no duty to retreat and has the right to stand his or her ground and use or threaten to use:
(a) Nondeadly force against another when and to the extent that the person reasonably believes that such conduct is necessary to defend himself or herself or another against the other’s imminent use of unlawful force; or
(b) Deadly force if he or she reasonably believes that using or threatening to use such force is necessary to prevent imminent death or great bodily harm to himself or herself or another or to prevent the imminent commission of a forcible felony.

http://www.leg.state.fl.us/statutes/index.cfm?App_mode=Display_Statute&URL=0700-0799/0776/Sections/0776.013.html

Sollte die y-Achse immer bei 0 beginnen?

“The designer of the visualization, by selecting a y-axis starting point, has control over the subjective importance of the resulting differences.” (Correll et al., 2020)

::: images-ref

Grafik aus Huff (1979), heruntergeladen von Observable blog.

:::

Absolute Werte

in der Regel ja

Veränderung der Werte

Ermessensfrage:

“If zero is in the neighborhood, invite it in!”Andrew Gelman

gapminder_2020_de <- gapminder_dat_full %>%
  filter(time >= 2000, time <= 2022, country == "deu") %>%
  arrange(desc(co2_pcap_cons))


ggplot(
  gapminder_2020_de, 
  aes(x = time, y = co2_pcap_cons, group = country, colour = country)
) +
  geom_line(linewidth = 1.5) +
  theme_bg() + 
  ylim(9, 14) +
  scale_color_manual(values = "#208C7A") +
  labs(title = "CO2 consumption per capita in Germany")

ggplot(
  gapminder_2020_de, 
  aes(x = time, y = co2_pcap_cons, group = country, colour = country)
) +
  geom_line(linewidth = 1.5) +
  theme_bg() + 
  ylim(0, 15) +
  scale_color_manual(values = "#208C7A") +
  labs(title = "CO2 consumption per capita in Germany")

library(ggbreak)


gapminder_2020 <- gapminder_dat_full %>%
  filter(time == 2020) %>%
  arrange(desc(co2_cons))

ggplot(
  gapminder_2020[1:10, ],
  aes(x = reorder(country, co2_cons), y = co2_cons)
) +
  geom_bar(stat = "identity", width = 1, color = "black", fill = "#208C7A") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  labs(title = "CO2 consumption per capita in the biggest emmiting countries") +
  theme_bg() +
  scale_y_break(c(1500, 5000)) 

gapminder_2020 <- gapminder_dat_full %>%
  filter(time == 2020) %>%
  arrange(desc(co2_cons))

ggplot(
  gapminder_2020[1:10, ],
  aes(x = reorder(country, co2_cons), y = co2_cons)
) +
  geom_bar(stat = "identity", width = 1, color = "black", fill = "#208C7A") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  labs(title = "CO2 consumption per capita in the biggest emmiting countries") +
  theme_bg()

Manipulation des Bildformats

set.seed(42)
my_weight <- data.frame(
  month = factor(c("Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"),
    levels = c("Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"),
    ordered = TRUE
  ),
  weight = rnorm(12, 90, 5) / 1000,
  group = rep(1, 12)
)

ggplot(
  my_weight,
  aes(x = month, y = weight, group = group)
) +
  geom_point() +
  geom_line() +
  labs(
    title = "Mein Gewicht 2024",
    subtitle = "In metrischen Tonnen",
    x = "Monat",
    y = "Gewicht in t"
  ) +
  ylim(0, 1000) +
  theme_bg() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))

Story

Goal

Darstellung von Größen

Die Größe von Kreisen einzuschätzen ist schwer!

Das gleiche gilt für Winkel!

Versuche, die Größen zu sortieren:

Nützlichkeit

“Design is a requirement, not a cosmetic addition.” nature methods

Foto von Kelly Sikkema auf Unsplash

Foto von Yucel Moran auf Unsplash

Funktionelles Design

Welche Funktion?

Foto von BVG

Foto von BVG

Dabei kann es helfen, sich von bereits vorhandenen Plots inspirieren zu lassen.
Handschriftliche Skizze ist auch erstmal hilfreich!

Simplify to clarify

  • Redundanzen Reduzieren
  • Fokus auf das Wesentliche https://www.data-to-viz.com/caveat/declutter.html

Visual Form

Beispiel von einem komplexen Plot bringen, den wir schon erzeugt haben, und dann reduzieren!

Salienz von Informationen

https://www.nature.com/articles/nmeth1010-773

Farben

Foto von Efe Kurnaz auf Unsplash

Farbwahrnehmung beim Menschen

Primärfarben des Lichts: Rot, Grün, Blau

Trichromatizität

Der Mensch hat in der Regel drei Zapfentypen, die unterschiedlich sensitiv für bestimmte Wellenlängen sind:

  • RGB erklären (und die Addition im Gehirn?

By BenRG - Own work, Public Domain, Link

Andere Tiere können auch mal mehr oder weniger haben:

Viele Tiere sehen nur grau:

  • Wale
  • Haie
  • Waschbären
  • Faultiere
  • Armadillos
  • Oktopusse
  • Kraken

Warum also überhaupt Farbwahrnehmung?

  • Ist subjektiv
  • Aber kann konsistent wahrgenommen werden: Eine Erdebeere im Schatten und im Sonnenlicht kann jeweils rot wahrgenommen werden. Grautöne sind stärker vom Licht abhängig.
  • More is not always better: Affen vom Amerikanischen Kontinent sind teilweise die und teilweise tetra: di kann besser INsekten ausmachen, tri Früchte gegen grünen background.
  • Farbwahrnehmung also abhängig vom Drumrum
  • Farben bringen nur einen Vorteil, wenn ein Individuum daraus meaning ableiten kann.

Farben

Ganglienzellen übertragen keine Informationen über absolute Helligkeit, sondern über relative Helligkeit. ‣ Wie stark unterscheiden sich zwei Bereiche in ihrer Helligkeit? ‣ Wie stark hat sich eine Helligkeit gerade geändert? Allgemeine Lehre daraus: ‣ Visualisierung kann nicht gut darin sein, absolute Werte zu kommunizieren (Tabellen können das besser) ‣ Hingegen ist das visuelle System (und das Gehirn allgemein) extrem gut darin, Unterschiede und Veränderungen zu erkennen –> Beispiel bringen (Simultankontrast)

  • Graustufen nicht für absolute oder kategorische Werte nutzen (mehr als vier können iwr nicht gut unterscheiden)

Farbton, Sättigung, Helligkeit

Kleine Farblehre

“All colors are the friends of their neighbors and the lovers of their opposites.” - Marc Chagall

Nach MalteAhrens at de.wikipedia. Vectorization by User:SidShakal - Raster version from Wikimedia Commons., Gemeinfrei, Link

Kleine Farbenlehre

Vincent van Gogh, Public domain, via Wikimedia Commons

  • Gegenüberliegende Farben intensivieren sich gegenseitig: eine Primärfarbe, und eine Sekundärfarbe, die aus den anderen beiden Primärfarben gemischt wird. Das erzeugt stärkere Kontraste. (Van Gogh bild)
  • Mit weiß und schwarz lassen sich die Schattierungen hinzumischen
  • triadisches Farbschema: Fom MIttelpunkt des Farbkreises Strahlen mit gleichem Winkel abgehen lassen (So viele, wie man Farben haben will). Das wählt dann Farben mit dem größten Kontrast aus. (https://www.kunstplaza.de/fashion-design/farbenlehre-farbtheorie-styleguides-fashion-design/)

Farbräume

  • RGB: Zusammenmischen von Licht der unterschiedlichen Farben. Alle drei zusammen ergeben weiß.
  • CMYK: Ausblenden aus dem weißen Farbspektrum durch Filter. Grundfarben: Gelb, Magenta (Purpur), Cyan (Blaugrün) (+K - Key (also Schwarz))

Was bringt uns das jetzt für unsere Abbildungen?

1. Die Farben sollten unterscheidbar, aber ähnlich sein!

1. Unterscheidbar, aber ähnlich

1. Unterscheidbar, aber ähnlich

Farbton, Sättigung und Helligkeit

Variiert immer Farbton, Sättigung und Helligkeit gleichzeitig.

Grau

2. Intuitives Verständnis: Diskrete Farben vs. Farbverläufe

  • Vor allem für diskrete Gruppen geeignet
  • Farbverläufe schwieriger (aber es gibt color scales dafür)
  • Bestimmte Farben (Grün = gut, Rot = Gefahr)

Größe

Farbe ist relativ

Beispiel: Grautöne vs Rottöne

Bild von Nature Methods.

Farbe ist relativ

Fragen: Warum ist das noch eine schelchte Color scale?

Siehe Colblindor.

Farbfehlsichtigkeit

protanopic right side

By Mark Fairchild, CC BY-SA 3.0, Link

  • Protanopie: Zapfentyp L (rot) fehlt: rot vs grün Schwäche

  • Deuteranopie: Zapfentyp M (grün) fehlt: rot vs grün Schwäche

  • Tritanopie: Zapfentyp S (blau) fehlt: blau vs grün, gelb vs violett Schwäche

  • Take the above scale and show that it is not great for color blindness

  • https://www.color-blindness.com/coblis-color-blindness-simulator/

library(colorblindcheck)
palette_check(theme_colors_red, plot = TRUE)  
          name n tolerance ncp ndcp min_dist mean_dist max_dist
1       normal 6   4.95969  15   15 4.959690  19.65649 41.78456
2 deuteranopia 6   4.95969  15   15 5.428903  19.54489 42.55279
3   protanopia 6   4.95969  15   14 3.976233  19.39475 41.15790
4   tritanopia 6   4.95969  15   15 5.557603  19.68098 40.93915

Das kann man sich natürlich auch zu Nutze machen (Passt aber vielleicht eher zu space? -> da nochmal zeigen, hier die Punkte enger zusammen )

Nicht zu viele!

Color for coding a third dimension

  • Wie viel macht sinn?
  • Scatterplot
  • Sättigung um dimensionalität der Farben darzustellen

Farbfehlsichtigkeiten

Farbsehtests

  • Farbwahrnehmung braucht Helligkeit und Fläche

Anwendung in ggplot2

Farben whlen

  • Name
  • rgb()
  • Nummer
  • Hex code

scale_color vs scale_fill

Farbskalen wählen

Manuell

ggplot(
  continent_co2_pct,
  aes(x = world_6region, 
      y = percentage, 
      fill = world_6region)) +
  geom_bar(stat = "identity", width = 1) +
  theme_minimal() +
  labs(title = "Share of global per capita CO2 consumption") +
  scale_fill_manual(values = c("red", "green", "blue", "yellow", "violet", "brown"))

my_color_scale <- c("East Asia & Pacific" ="red", 
                    "South Asia" = "green", 
                    "Sub-Saharan Africa" = "blue", 
                    "Europe & Central Asia" = "yellow", 
                    "Middle East & North Africa" = "violet", 
                    "Americas" = "brown")
ggplot(
  continent_co2_pct,
  aes(x = world_6region, 
      y = percentage, 
      color = world_6region)) +
  geom_bar(stat = "identity", width = 1) +
  theme_minimal() +
  labs(title = "Share of global per capita CO2 consumption") +
  scale_color_manual(values = my_color_scale)

Graustufen

ggplot(
  continent_co2_pct,
  aes(x = world_6region, 
      y = percentage, 
      fill = world_6region)) +
  geom_bar(stat = "identity", width = 1) +
  theme_minimal() +
  labs(title = "Share of global per capita CO2 consumption") +
  scale_fill_grey()

Externe Farb-Paletten

R Color Brewer

library(RColorBrewer)
display.brewer.all(colorblindFriendly=FALSE)

library(RColorBrewer)

ggplot(
  continent_co2_pct,
  aes(x = world_6region, 
      y = percentage, 
      fill = world_6region)) +
  geom_bar(stat = "identity", width = 1) +
  theme_minimal() +
  labs(title = "Share of global per capita CO2 consumption") +
  scale_fill_brewer(palette = "PuOr")

Externe Farb-Paletten

paletteer

Sammlung von Paletten aus diversen Paketen

library(paletteer)

ggplot(
  continent_co2_pct,
  aes(x = world_6region, 
      y = percentage, 
      fill = world_6region)) +
  geom_bar(stat = "identity", width = 1) +
  theme_minimal() +
  labs(title = "Share of global per capita CO2 consumption") +
  scale_fill_paletteer_d("LaCroixColoR::Orange")

Arten von Skalen

  • Intervallskaliert/Verhältnisskaliert: Es gibt Nullpunkt: Divergierende Skalen

    • Kein Nullpunkt: Gleichmäßige Abstände zwischen Farbwerten
  • Ordinal: Richtung und gute Unterscheidbarkeit im Farbraum

  • Nominalskaliert: Spriale/Diagnoale im Farbraum

  • Diskret

  • Continuous

  • Binned Vorgefertigte Paletten:

Sequenziell
Qualitativ
Diverging

Weiter Tools

  • Colorbrewer
  • Huemint

Farben: Zusammenfassung

Übung

  1. Teste, ob deine bisherige Abbildung für Menschen mit Farbfehlsichtigkeit lesbar ist.
  2. Baue eine eigene Color-Palette und wende sie auf deinen Plot an. Achte dabei auf Unterscheidbarkeit (Auch color blind), aber auch auf Design.

Gruppieren

Verteilungen Plotten

Eine Grafik ist kann nur einen Teil der Zusammenhänge darstellen

Im Schlechtesten Fall für Manipulation nutzbar

Black swann

Was mögt ihr an dieser Grafik/nicht?

Plot-Typen

Choosing the correct plot

https://www.data-to-viz.com/

Pulikum

Geschichte

  • Gleiche sache auf verschiedene Arten darstellen -> was wirkt am meisten?

Purpose

  • Exploratory vs. Explanatory
  • Kann oft über reines Zeigen der Daten hinausgehen.

Size

Also include population size in the scatterplot - Was will ich hier sagen? - Beachten: Oben hatte ich auch schon was zu size, bei den Kreisen.

Tufte

  • So wenig Tinte wie möglich nutzen
  • Alles nicht essentielle entfernen
  • Redundanzen entferenen

Beispiel zeigen: Eins ist aufgeblasen, man braucht Zeit zur Orientierung. Ein anderes theme_tufte

Data-ink ratio

Beispiel zum Minimieren zeigen - Raincloud plots

Caveats

https://www.data-to-viz.com/caveats.html

Immer einen Plot zeigen, dann zusammen überlgen was daran schlecht ist, was verbessert werden kann.

Takeaways

Nochmal bei Tufte nachschauen - Kommunizieren von der größtmöglichen Anzahl von Ideen in kürzester Zeit. - Maximieren von Data-ink ratio - Minimieren von Chart-junk - Erhöhe Datendichte, Vervielfältige kleine Versionen der Abbildung für verschiedene Gruppen

Caveat Plots: See data to viz

Storytelling

Rolfs 7 Nature

Using Text/Annotations

Nutzen von Eigenschaften der Wahrnehmung

  • Bewusste Wahrnehmung ignoriert Informationen und fügt hinzu
  • Nähe
  • Salienz (Farbe, Größe, Neigung) (Szafir, Haroz, Gleicher & Franconeri (2016))
  • Hier würde das USA Bild passen (evtl. mit einem anderen Beispiel, das den Unterschied deutlich macht, z.B. keine andere Farbe, näher dran durch andere Achse)
  • Harold et al., 2016: Lenken von Aufmerksamkeit
  • Nothelfer et al., 2017: Redundatne Kodierung führt zu deutlich höherer Genauigkeit der Antworten und kürzeren Reaktionszeiten

Übung

Schlechte Grafiken verbessern: https://www.data-to-viz.com/caveats.html

Correll, M., Bertini, E., & Franconeri, S. (2020). Truncating the y-axis: Threat or menace? Proceedings of the 2020 CHI conference on human factors in computing systems, 1–12.
Huff, D. (1979). How to lie with statistics / by Darrell Huff.